
/**********************************************************************************\
** 文件名称: snake.c
** 创建人员: YCG
** 创建日期: 2019-01-20
** 文档描述:
** 版本代号: V0.1
** 版本说明: 初始版本
\********************************************************************************/
#include "snake.h"
#include "oled.h"

static void Snake_CmdDeal(CmdHead_t* yCmdHead, uint8_t yCmd);

void Snake_Init(SnakeHead_t* ySnakeHead, void (*yShow)(SnakeHead_t *ySnakeHead, uint8_t State), uint8_t yWidth, uint8_t yHigh, uint8_t yMaxLength)
{
    ySnakeHead->Head = 0;   //头部缓存地址为零    //只有一个节点
    ySnakeHead->End =  0;   //尾部缓存地址为零    //只有一个节点
    ySnakeHead->Num =  1;   //当前节点数量
    ySnakeHead->Dire =    SNAKE_RIGHT;   //蛇头方向
    ySnakeHead->OldDire = SNAKE_RIGHT;   //上一次的蛇头方向

    ySnakeHead->Show = yShow;   //设置显示函数的地址

    ySnakeHead->Width = yWidth;         //初始化宽度
    ySnakeHead->High  = yHigh;          //初始化高度
    ySnakeHead->State = SNAKE_NO_Init;  //初始化状态
    ySnakeHead->Level = 0;              //初始化等级 为0

    ySnakeHead->MaxScore = 0;           //初始化最高分
    ySnakeHead->Score = 0;              //初始化当前得分
    ySnakeHead->MaxLength = yMaxLength; //蛇的最大长度

    Cmd_Init(ySnakeHead->Cmd, Snake_CmdDeal);  //初始化命令控制
    ySnakeHead->Cmd->Target = ySnakeHead;      //设置命令控制的目标对象

    Mymemset((uint8_t*)ySnakeHead->Dot, 0, SNAKE_MaxNum << 2); //将所有的节点数据清零

    ySnakeHead->Dot[0].Pre = NULL;	   //只有一个节点
    ySnakeHead->Dot[0].Next = NULL;    //只有一个节点
    ySnakeHead->Dot[0].x = yWidth / 2; //起点位于中心
    ySnakeHead->Dot[0].y = yHigh / 2;  //起点位于中心

    srand(HAL_GetTick());   //设置随机数种子
    SNAKE_Printf("Snake_Init:0x%8x\n", ySnakeHead);
}

void Snake_Start(SnakeHead_t* ySnakeHead)
{
    CmdHead_t * const yCmdHead = ySnakeHead->Cmd;
    uint8_t yState = ySnakeHead->State;

    while(yState != SNAKE_Exit) {
        switch (yState)
        {
        case SNAKE_NO_Init:                                         //对地图进行初始化
            ySnakeHead->Show(ySnakeHead, SNAKE_ShowBegin);          //显示开始欢迎界面
            SNAKE_Printf("State:%d Begin\n", yState);

            ySnakeHead->State = SNAKE_WaitStart;                    //进入等待进入游戏状态
            ySnakeHead->Show(ySnakeHead, SNAKE_ShowWaitStart);      //显示等待进入游戏界面
            SNAKE_Printf("State:%d WaitStart\n", yState);
            break;

        case SNAKE_WaitStart:                                       //等待确认进入游戏

            if(Cmd_Get(yCmdHead, SNAKE_OK)) {                       //OK命令确认进入游戏
                Snake_SetLevel(ySnakeHead, 0);                      //将关卡归零
                ySnakeHead->State = SNAKE_Runing;                   //进入运行游戏状态
                ySnakeHead->Show(ySnakeHead, SNAKE_ShowLevel);      //根据游戏等级初始化界面
                SNAKE_Printf("State:%d Level:%d\n", yState, ySnakeHead->Level);
            }

            if(Cmd_Get(yCmdHead, SNAKE_BACK)) {                      //是否退出游戏
                ySnakeHead->State = SNAKE_WaitEnd;                   //进入等待退出游戏状态
                ySnakeHead->Show(ySnakeHead, SNAKE_ShowWaitEnd);     //显示等待退出游戏界面
                SNAKE_Printf("State:%d WaitEnd\n", yState);
            }
            break;

        case SNAKE_Runing:     //游戏正在运行中,按键在回调中处理

            switch (Snake_TryMoveStep(ySnakeHead))                   //获取移动到下一个点的状态
            {
            case SNAKE_RunMove:                                      //正常移动

                Snake_AddHeadDot(ySnakeHead);                        //头部添加一个节点
                ySnakeHead->Show(ySnakeHead, SNAKE_ShowDelEnd);      //先将尾部节点显示删除，再把相应的尾部缓存删除
                Snake_DelEndDot(ySnakeHead);                         //删除尾部节点
                ySnakeHead->Show(ySnakeHead, SNAKE_ShowAddNewHead);  //头部显示更新
                SNAKE_Printf("State:%d RunMove\n", yState);
                break;

            case SNAKE_RunEatfood:
                Snake_AddHeadDot(ySnakeHead);     //头部添加一个节点
                Snake_CreatFood(ySnakeHead);      //计算出新的食物坐标

                ySnakeHead->Show(ySnakeHead, SNAKE_ShowAddNewHead);  //头部显示更新

                ySnakeHead->Score += ySnakeHead->Score + 1;          //每个等级的分数是不同的
                SNAKE_Printf("State:%d Eatfood Score:%d\n", yState, ySnakeHead->Score);

                if(ySnakeHead->Num++ >= ySnakeHead->MaxLength)          //计算节点总数,判断是否达到过关条件
                {
                    Snake_SetLevel(ySnakeHead, SNAKE_LEVEL_ADD);        //关卡提升
                    ySnakeHead->Show(ySnakeHead, SNAKE_ShowUpLevel);    //关卡提升界面
                    ySnakeHead->State = SNAKE_UpLevel;                  //进入等级提升状态
                    SNAKE_Printf("State:%d Level:%d\n", yState, ySnakeHead->Level);
                }
                break;
            case SNAKE_RunOver:
                ySnakeHead->State = SNAKE_GameOver;                  //进入游戏结束状态
                ySnakeHead->Show(ySnakeHead, SNAKE_ShowOver);        //显示游戏结束
                SNAKE_Printf("State:%d GameOver\n", yState);
                break;

            }

        case SNAKE_UpLevel:                                         //等级提升
            if(ySnakeHead->Level < SNAKE_MaxLevel) {                //判断是否通过所有的关卡
                if(Cmd_Get(yCmdHead, SNAKE_OK)) {                   //确认进入下一关游戏
                    ySnakeHead->State = SNAKE_Runing;
                    ySnakeHead->Show(ySnakeHead, SNAKE_ShowLevel);      //根据游戏等级初始化界面
                    SNAKE_Printf("State:%d Level:%d\n", yState, ySnakeHead->Level);
                }
            } else {
                ySnakeHead->Show(ySnakeHead, SNAKE_ShowComplete);    //界面
                ySnakeHead->State = SNAKE_Complete;
                SNAKE_Printf("State:%d Complete\n", yState);
            }
            break;

        case SNAKE_GameOver:   //游戏结束

            if(Cmd_Get(yCmdHead, SNAKE_OK)) {                       //确认重新开始游戏
                ySnakeHead->State = SNAKE_WaitStart;                    //进入等待进入游戏状态
                ySnakeHead->Show(ySnakeHead, SNAKE_ShowWaitStart);      //显示等待进入游戏界面
                SNAKE_Printf("State:%d WaitStart\n", yState);
            }
            if(Cmd_Get(yCmdHead, SNAKE_BACK)) {                     //退出游戏
                ySnakeHead->State = SNAKE_WaitEnd;                   //进入等待退出游戏状态
                ySnakeHead->Show(ySnakeHead, SNAKE_ShowWaitEnd);     //显示等待退出游戏界面
                SNAKE_Printf("State:%d WaitEnd\n", yState);
            }
            break;

        case SNAKE_Complete:   //游戏通关
            if(Cmd_Get(yCmdHead, SNAKE_OK)) {                       //确认重新开始游戏
                ySnakeHead->State = SNAKE_WaitStart;                    //进入等待进入游戏状态
                ySnakeHead->Show(ySnakeHead, SNAKE_ShowWaitStart);      //显示等待进入游戏界面
                SNAKE_Printf("State:%d WaitStart\n", yState);
            }
            if(Cmd_Get(yCmdHead, SNAKE_BACK)) {  //退出游戏
                ySnakeHead->State = SNAKE_WaitEnd;                   //进入等待退出游戏状态
                ySnakeHead->Show(ySnakeHead, SNAKE_ShowWaitEnd);     //显示等待退出游戏界面
                SNAKE_Printf("State:%d WaitEnd\n", yState);
            }
            break;

        case SNAKE_WaitEnd:   //等待确认结束游戏
            if(Cmd_Get(yCmdHead, SNAKE_OK)) {                        //确认结束游戏
                ySnakeHead->State = SNAKE_Exit;
                SNAKE_Printf("State:%d Exit\n", yState);
            }
            if(Cmd_Get(yCmdHead, SNAKE_BACK)) {                    //退出游戏

                ySnakeHead->State = SNAKE_Runing;
                SNAKE_Printf("State:%d BACK\n", yState);
            }

            break;
        }
        yState = ySnakeHead->State;
    }
}

void Snake_SetLevel(SnakeHead_t* ySnakeHead, uint8_t yLevel)
{
    ySnakeHead->Head = 0;   //头部缓存地址为零    //只有一个节点
    ySnakeHead->End =  0;   //尾部缓存地址为零    //只有一个节点
    ySnakeHead->Num =  1;   //当前节点数量
    ySnakeHead->Dire =    SNAKE_RIGHT;   //蛇头方向
    ySnakeHead->OldDire = SNAKE_RIGHT;   //上一次的蛇头方向

    ySnakeHead->Level++;               //设置等级
    if(yLevel != SNAKE_LEVEL_ADD)  {   //不是等级提升命令
        ySnakeHead->Level = yLevel;  //将等级设定为指定的值
    }

    Mymemset((uint8_t*)ySnakeHead->Dot, 0, SNAKE_MaxNum << 2); //将所有的节点数据清零

    ySnakeHead->Dot[0].Pre = NULL;	  //初始化起始节点
    ySnakeHead->Dot[0].Next = NULL;
    ySnakeHead->Dot[0].x = ySnakeHead->Width / 2;
    ySnakeHead->Dot[0].y = ySnakeHead->High  / 2;
}


uint8_t Snake_IsInRang(SnakeHead_t* ySnakeHead, uint8_t x, uint8_t y)
{
    uint8_t yreturn = SNAKE_FALSE;                                        //返回状态设为失败
    uint8_t  const yWidth = ySnakeHead->Width, yHigh = ySnakeHead->High;  //获取地图大小
    Snakedot_t* yDot = ySnakeHead->Dot;                                   //获取数据数组的地址

    if(x < yWidth && y < yHigh) {      //在范围以内
        uint8_t yIndex ;
        for(yIndex = ySnakeHead->Head; yIndex != ySnakeHead->End; yIndex = yDot[yIndex].Next) {  //遍历整个蛇身
            if((x == yDot[yIndex].x) && (y == yDot[yIndex].y)) {       //判断点是不是在蛇身上
                break;                                                 // 在蛇身上,结束循环
            }
        }
        if(yIndex == ySnakeHead->End) {                              //如果达到了尾部，点不在蛇身上
            if((x != yDot[yIndex].x) || (y != yDot[yIndex].y)) {     //判断点是不是在蛇尾
                yreturn = SNAKE_TRUE;                                //如果都不在，则点在合适的范围内
            }
        }
    }
    return yreturn;
}


void Snake_CreatFood(SnakeHead_t* ySnakeHead) {
    uint8_t  const yWidth = ySnakeHead->Width, yHigh = ySnakeHead->High;
    uint8_t x, y;  //  坐标

    do {
        x = rand() % yWidth;    //随机获取x坐标
        y = rand() % yHigh;    //随机获取y坐标
    } while(Snake_IsInRang(ySnakeHead, x, y) == SNAKE_FALSE);  //直到产生一个合适的食物位置

    ySnakeHead->FoodX = x;
    ySnakeHead->FoodY = y;
}


uint8_t Snake_TryMoveStep(SnakeHead_t* ySnakeHead)
{
    Snakedot_t *ySnakedot = ySnakeHead->Dot;   //获取缓存数组的位置
    uint8_t yHead = ySnakeHead->Head;          //头部指向的缓存位置
    uint8_t yReturn = SNAKE_RunMove;

    ySnakeHead->NextX = ySnakedot[yHead].x; 	//根据原来头部坐标，计算移动的下一个点的坐标x
    ySnakeHead->NextY = ySnakedot[yHead].y; 	//根据原来头部坐标，计算移动的下一个点的坐标y

	  ySnakeHead->OldDire=ySnakeHead->Dire;     //保留一下此时的方向
	
    switch(ySnakeHead->Dire) {                //根据当前方向执行相应的动作
    case SNAKE_UP:
        ySnakeHead->NextY++;
        break;
    case SNAKE_DOWM:
        ySnakeHead->NextY--;
        break;
    case SNAKE_RIGHT:
        ySnakeHead->NextX++;
        break;
    case SNAKE_LEFT:
        ySnakeHead->NextX--;
        break;
    }

    if(Snake_IsInRang(ySnakeHead, ySnakeHead->NextX, ySnakeHead->NextY)) {      //判断有没有撞墙和自身
        if((ySnakeHead->NextX == ySnakeHead->FoodX) && (ySnakeHead->NextY == ySnakeHead->FoodY)) {   //判断有没有吃到食物
            yReturn = SNAKE_RunEatfood;     //返回吃到食物
        } else {
            yReturn = SNAKE_RunMove;        //返回正常移动
        }

    } else {
        yReturn = SNAKE_RunOver;            //返回撞墙
    }
    return yReturn;
}

void Snake_AddHeadDot(SnakeHead_t* ySnakeHead)
{
    Snakedot_t *ySnakedot = ySnakeHead->Dot;   //获取缓存数组的位置
    uint8_t yHead = ySnakeHead->Head;          //头部指向的缓存位置
    uint8_t yDot;                              //节点开始找

    for(yDot = 0; Dot_IsNotEmpty(ySnakedot[yDot]); yDot++) {}    //查找空闲缓存位置

    ySnakedot[yDot].Next =	yHead; 	           //新节点的下一个是旧的头部
    ySnakedot[yHead].Pre =	yDot; 		         //旧的头部的上一个是新节点
    ySnakedot[yDot].x = ySnakeHead->NextX; 	   //保存新坐标x
    ySnakedot[yDot].y = ySnakeHead->NextY; 	   //保存新坐标y

    ySnakeHead->Head = yDot;                   //更新首部位置
}

void Snake_DelEndDot(SnakeHead_t* ySnakeHead)
{
    Snakedot_t *ySnakedot = ySnakeHead->Dot;   //获取缓存数组的位置
    uint8_t yEnd = ySnakeHead->End;            //尾部指向的缓存位置

    ySnakeHead->End = ySnakedot[yEnd].Pre;     //更新尾部指向的缓存位置，指向旧尾部的上一个

    Dot_Clear(ySnakedot[yEnd]);                //清除原尾部缓存数据
}


static void Snake_CmdDeal(CmdHead_t* yCmdHead, uint8_t yCmd)
{
    SnakeHead_t* ySnakeHead = (SnakeHead_t*)(yCmdHead->Target);

    if(ySnakeHead->State == SNAKE_Runing) {
        if(Cmd_Get(yCmdHead, SNAKE_UP)) {      //向上
            if(ySnakeHead->Dire != SNAKE_DOWM)     //不能反着
                ySnakeHead->Dire = SNAKE_UP;
        }
        if(Cmd_Get(yCmdHead, SNAKE_DOWM)) {    //向下
            if(ySnakeHead->Dire != SNAKE_UP)     //不能反着
                ySnakeHead->Dire = SNAKE_DOWM;
        }
        if(Cmd_Get(yCmdHead, SNAKE_LEFT)) {    //向左
            if(ySnakeHead->Dire != SNAKE_RIGHT)     //不能反着
                ySnakeHead->Dire = SNAKE_LEFT;
        }

        if(Cmd_Get(yCmdHead, SNAKE_RIGHT)) {  //向右
            if(ySnakeHead->Dire != SNAKE_LEFT)     //不能反着
                ySnakeHead->Dire = SNAKE_RIGHT;
        }
    }
}
/********************************End of File************************************/
